﻿package org.core {
	
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.ContextMenuEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.external.ExternalInterface;
	import flash.ui.ContextMenu;
	import flash.ui.ContextMenuItem;
	import flash.ui.Keyboard;
	import flash.utils.getTimer;
	import flash.utils.Timer;
	import org.manager.CardFactory;
	import org.ui.Card;
	import org.ui.Digit;
	import org.ui.Flag;
	import org.ui.Mine;
	
	/**
	 * ...
	 * @author TianYu
	 */
	public class MainBase extends MovieClip {
		private var rows:int = 25;          // 9   25
		private var cols:int = 25;          // 9   25
		private var mines:int = 80;         // 10  80
		private var gridpx:Number = 20;
		
		private var cf:CardFactory;
		private var mineArray:Vector.<int>;
		private var cardArray:Vector.<Card>;
		private var indexArray:Vector.<int>;
		private var mainScene:Sprite;
		private var showedCount:int = 0;
		private var flagCount:int = 0;
		private var card:Card;
		
		private var startTime:int;
		private var timer:Timer;
		
		public function MainBase() 
		{
			updateRightClickMenu();
			initVariables();
			drawUI();
			startGame();
		}
		
		private function updateRightClickMenu():void 
		{
			stage.showDefaultContextMenu = false;
			var menu:ContextMenu = new ContextMenu();
			menu.hideBuiltInItems();
			menu.customItems.push(new ContextMenuItem("Author: Tian Yu", true, true));
			var item:ContextMenuItem = new ContextMenuItem("Feedback: tianyu0714@gmail.com", false, true);
			item.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onItemSelected);
			menu.customItems.push(item);
			
			function onItemSelected(e:ContextMenuEvent):void 
			{
				ExternalInterface.call("window.open", "mailto:tianyu0714@gmail.com");
			}
			
			this.contextMenu = menu;
		}
		
		private function initVariables():void 
		{
			cf = new CardFactory();
			mineArray = new Vector.<int>(mines);
			mineArray.fixed = true;
			cardArray = new Vector.<Card>(rows * cols);
			cardArray.fixed = true;
			mainScene = new Sprite();
			addChild(mainScene);
		}
		
		private function drawUI():void 
		{
			stage.scaleMode = StageScaleMode.SHOW_ALL;
			
			graphics.lineStyle(0, 0, 0.5);
			for (var i:int = 0; i < rows + 1; i++) 
			{
				graphics.moveTo(0, i * gridpx);
				graphics.lineTo(cols * gridpx, i * gridpx);
			}
			
			for (var j:int = 0; j < cols + 1; j++) 
			{
				graphics.moveTo(j * gridpx, 0);
				graphics.lineTo(j * gridpx, rows * gridpx);
			}
		}
		
		private function startGame():void 
		{
			var nums:int = mainScene.numChildren;
			
			for (i = 0; i < nums; i++) 
			{
				mainScene.removeChildAt(0);
			}
			
			for (i = 0; i < mineArray.length; i++) 
			{
				mineArray[i] = 0;
			}
			
			for (i = 0; i < cardArray.length; i++) 
			{
				cardArray[i] = null;
			}
			
			indexArray = new Vector.<int>(rows * cols);
			for (i = 0; i < indexArray.length; i++) 
			{
				indexArray[i] = i;
			}
			
			trace("----------Game Start---------");
			showedCount = 0;
			flagCount = 0;
			resetTexts();
			bindEvents();
			distributeMine();
			assignMineAroundDigits();
			assignBlanks();
			
			startTime = getTimer();
			
			if (timer)
			{
				timer.stop();
				timer.removeEventListener(TimerEvent.TIMER, timer_timer);
				timer = null;
			}
			
			timer = new Timer(100);
			timer.addEventListener(TimerEvent.TIMER, timer_timer);
			timer.start();
		}
		
		private function timer_timer(e:TimerEvent):void 
		{
			timeElapse_txt.text = "Time Elapse: " + formatTime((getTimer() - startTime) / 1000);
		}
		
		private function formatTime(seconds:int):String 
		{
			var hour:int = (int)(seconds / 3600);
			var minute:int = (int)((seconds - hour * 3600) / 60);
			var second:int = seconds - hour * 3600 - minute * 60;
			return formatNumber(hour) + ":" + formatNumber(minute) + ":" +formatNumber(second);
		}
		
		private function formatNumber(num:int):String
		{
			return num <= 9 ? "0" + num.toString():num.toString();
		}
		
		private function resetTexts():void 
		{
			minesLeft_txt.text = "Mines Left: " + mines;
			gameInfo_txt.text = "MineSweepering...";
			timeElapse_txt.text = "Time Elapse: " + formatTime(0);
		}
		
		
		private function distributeMine():void 
		{
			for (var i:int = 0; i < mineArray.length; i++) 
			{
				mineArray[i] = indexArray.splice(Math.random() * (indexArray.length - 1), 1)[0];
			}
			
			for (i = 0; i < mineArray.length; i++) 
			{
				var card:Card = cf.produceCard("mine");
				var obj:Object = indexToPosition(mineArray[i]);
				card.x = obj.x;
				card.y = obj.y;
				card.type = obj.position + 9;
				cardArray[mineArray[i]] = card;
				mainScene.addChild(card);
			}
		}
		
		private function assignMineAroundDigits():void 
		{
			for (var i:int = 0; i < mineArray.length; i++) 
			{
				var minePositionObj:Object = indexToPosition(mineArray[i]);
				var minePosition:int = minePositionObj.position;
				var minerow:int = minePosition / 1000;
				var minecol:int = (minePosition  % 1000) / 10;
				var roundnum:int = 0;
				var cardObj:Object;
				var j:int = 0;
				
				if ((minerow == 1 && minecol == 1) || (minerow == rows && minecol == cols) || (minerow == 1 && minecol == cols) || (minerow == rows && minecol == 1))
					roundnum = 3;
				else if (minerow == 1 || minerow == rows || minecol == 1 || minecol == cols)
					roundnum = 5;
				else
					roundnum = 8;
				
				if (roundnum == 3)
				{
					
					if (minerow == 1 && minecol == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 4);
							assign();
						}
					}
					
					if (minerow == 1 && minecol == cols)
					{
						for (j = 0; j < roundnum; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 6);
							assign();
						}
					}
					
					if (minerow == rows && minecol == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 2);
							assign();
						}
					}
					
					if (minerow == rows && minecol == cols)
					{
						cardObj = centreAroundObj(minePositionObj, 1);
						assign();
						cardObj = centreAroundObj(minePositionObj, 2);
						assign();
						cardObj = centreAroundObj(minePositionObj, 8);
						assign();
					}
					
				}
				
				if (roundnum == 5)
				{
					if (minerow == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 4);
							assign();
						}
					}
					
					if (minerow == rows)
					{
						for (j = 0; j < roundnum - 1; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 1);
							assign();
						}
						
						cardObj = centreAroundObj(minePositionObj, 8);
						assign();
					}
					
					if (minecol == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 2);
							assign();
						}
					}
					
					if (minecol == cols)
					{
						for (j = 0; j < 2; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 1);
							assign();
						}
						
						for (j = 0; j < 3; j++) 
						{
							cardObj = centreAroundObj(minePositionObj, j + 6);
							assign();
						}
					}
				}
				
				if (roundnum == 8)
				{
					for (j = 0; j < roundnum; j++) 
					{
						cardObj = centreAroundObj(minePositionObj, j + 1);
						assign();
					}
				}
				
				
				trace("minerow: " + minerow + " minecol: " + minecol + " roundnum: " + roundnum);
				
				function assign():void 
				{
					var positionCard:Card = cardArray[positionToIndex(cardObj.position)];
					
					if (positionCard == null)
					{
						var card:Card = cf.produceCard("digit");
						(card as Digit).digit = (card as Digit).digit + 1;
						card.type = (int)(cardObj.position / 10) * 10 + (card as Digit).digit;
						cardArray[positionToIndex(cardObj.position)] = card;
						mainScene.addChild(card);
						card.x = cardObj.x;
						card.y = cardObj.y;
					}
					else if(!(positionCard is Mine))
					{
						(positionCard as Digit).digit = (positionCard as Digit).digit + 1;
						(positionCard as Digit).type = (int)(cardObj.position / 10) * 10 + positionCard.digit;
					}
				}
			}
		}
		
		
		
		private function assignBlanks():void 
		{
			for (var i:int = 0; i < cardArray.length; i++) 
			{
				if (cardArray[i] == null)
				{
					var card:Card = cf.produceCard("blank");
					var cardObj:Object = indexToPosition(i);
					card.x = cardObj.x;
					card.y = cardObj.y;
					card.type = cardObj.position;
					cardArray[i] = card;
					mainScene.addChild(card);
				}
			}
		}
		
		
		private function bindEvents():void 
		{
			mainScene.removeEventListener(MouseEvent.CLICK, mainScene_click);
			mainScene.removeEventListener(MouseEvent.MOUSE_OVER, mainScene_mouseOver);
			mainScene.removeEventListener(MouseEvent.MOUSE_OUT, mainScene_mouseOut);
			mainScene.removeEventListener(MouseEvent.DOUBLE_CLICK, mainScene_doubleClick);
			restart_btn.removeEventListener(MouseEvent.CLICK, restart_btn_click);
			mainScene.addEventListener(MouseEvent.CLICK, mainScene_click);
			mainScene.addEventListener(MouseEvent.MOUSE_OVER, mainScene_mouseOver);
			mainScene.addEventListener(MouseEvent.MOUSE_OUT, mainScene_mouseOut);
			mainScene.addEventListener(MouseEvent.DOUBLE_CLICK, mainScene_doubleClick);
			restart_btn.addEventListener(MouseEvent.CLICK, restart_btn_click);
		}
		
		
		
		private function restart_btn_click(e:MouseEvent):void 
		{
			startGame();
		}
		
		
		private function mainScene_mouseOver(e:MouseEvent):void 
		{
			stage.removeEventListener(KeyboardEvent.KEY_UP, stage_keyUp);
			
			if (!(e.target.parent is Card))
				return;
			card = e.target.parent;
			
			stage.removeEventListener(KeyboardEvent.KEY_UP, stage_keyUp);
			stage.addEventListener(KeyboardEvent.KEY_UP, stage_keyUp);
		}
		
		private function mainScene_mouseOut(e:MouseEvent):void 
		{
			stage.removeEventListener(KeyboardEvent.KEY_UP, stage_keyUp);
		}
		
		private function stage_keyUp(e:KeyboardEvent):void 
		{
			if (e.keyCode == Keyboard.SPACE)
			{
				if (card && !card.flaged && !card.showed && (mines - flagCount > 0))
				{
					card.addChild(new Flag());
					card.flaged = true;
					flagCount++;
					minesLeft_txt.text = "Mines Left: " + (mines - flagCount);
				}
				else if (card && card.flaged && !card.showed && (mines - flagCount > 0))
				{
					card.removeChildAt(card.numChildren - 1);
					card.flaged = false;
					flagCount--;
					minesLeft_txt.text = "Mines Left: " + (mines - flagCount);
				}
			}
		}
		
		private function mainScene_doubleClick(e:MouseEvent):void 
		{
			if (!(e.target.parent.parent is Digit))
				return;
			
			var card:Digit = e.target.parent.parent;
			
			var minerow:int = card.type / 1000;
			var minecol:int = (card.type  % 1000) / 10;
			var roundnum:int = 0;
			var cardObj:Object;
			var roundFlagCount:int = 0;
			var j:int = 0;
			
			if ((minerow == 1 && minecol == 1) || (minerow == rows && minecol == cols) || (minerow == 1 && minecol == cols) || (minerow == rows && minecol == 1))
				roundnum = 3;
			else if (minerow == 1 || minerow == rows || minecol == 1 || minecol == cols)
				roundnum = 5;
			else
				roundnum = 8;
			
			if (roundnum == 3)
			{
				
				if (minerow == 1 && minecol == 1)
				{
					for (j = 0; j < roundnum; j++) 
					{
						count(j+4);
					}
					
					if (roundFlagCount == card.digit)
					{
						for (j = 0; j < roundnum; j++) 
						{
							spread(j+4);
						}
					}
				}
				
				if (minerow == 1 && minecol == cols)
				{
					for (j = 0; j < roundnum; j++) 
					{
						count(j+6);
					}
					
					if (roundFlagCount == card.digit)
					{
						for (j = 0; j < roundnum; j++) 
						{
							spread(j+6);
						}
					}
				}
				
				if (minerow == rows && minecol == 1)
				{
					for (j = 0; j < roundnum; j++) 
					{
						count(j+2);
					}
					
					if (roundFlagCount == card.digit)
					{
						for (j = 0; j < roundnum; j++) 
						{
							spread(j+2);
						}
					}
				}
				
				if (minerow == rows && minecol == cols)
				{
					count(j+1);
					count(j+2);
					count(j+8);
					
					if (roundFlagCount == card.digit)
					{
						spread(j+1);
						spread(j+2);
						spread(j+8);
					}
				}
				
			}
			
			if (roundnum == 5)
			{
				if (minerow == 1)
				{
					for (j = 0; j < roundnum; j++) 
					{
						count(j+4);
					}
					
					if (roundFlagCount == card.digit)
					{
						for (j = 0; j < roundnum; j++) 
						{
							spread(j+4);
						}
					}
				}
				
				if (minerow == rows)
				{
					for (j = 0; j < roundnum - 1; j++) 
					{
						count(j+1);
					}
					
					count(8);
					
					if (roundFlagCount == card.digit)
					{
						for (j = 0; j < roundnum - 1; j++) 
						{
							spread(j+1);
						}
						
						spread(8);
					}
				}
				
				if (minecol == 1)
				{
					for (j = 0; j < roundnum; j++) 
					{
						count(j+2);
					}
					
					if (roundFlagCount == card.digit)
					{
						for (j = 0; j < roundnum; j++) 
						{
							spread(j+2);
						}
					}
				}
				
				if (minecol == cols)
				{
					for (j = 0; j < 2; j++) 
					{
						count(j+1);
					}
					
					for (j = 0; j < 3; j++) 
					{
						count(j+6);
					}
					
					if (roundFlagCount == card.digit)
					{
						for (j = 0; j < 2; j++) 
						{
							spread(j+1);
						}
						
						for (j = 0; j < 3; j++) 
						{
							spread(j+6);
						}
					}
				}
			}
			
			if (roundnum == 8)
			{
				for (j = 0; j < roundnum; j++) 
				{
					count(j+1);
				}
				
				if (roundFlagCount == card.digit)
				{
					for (j = 0; j < roundnum; j++) 
					{
						spread(j+1);
					}
				}
			}
			
			function count(index:int):void
			{
				var obj:Object = new Object();
				obj.position = card.type;
				cardObj = centreAroundObj(obj, index);
				var positionCard:Card = cardArray[positionToIndex(cardObj.position)];
				if (positionCard.flaged)
					roundFlagCount++;
			}
			
			function spread(index:int):void
			{
				obj = new Object();
				obj.position = card.type;
				cardObj = centreAroundObj(obj, index);
				positionCard = cardArray[positionToIndex(cardObj.position)];
				decideCard(positionCard);
			}
			
		}
		
		private function mainScene_click(e:MouseEvent):void 
		{
			if (!(e.target.parent is Card))
				return;
			
			var card:Card = e.target.parent;
			
			decideCard(card);
		}
		
		private function decideCard(card:Card):void 
		{
			if (!card.clicked && !card.flaged)
			{
				card.clicked = true;
				card.showed = true;
				
				if (card is Mine)
				{
					GameOver();
				}
				else
				{
					showedCount++;
					noMinePropagate(card);
					if (showedCount == cardArray.length - mineArray.length)
					{
						GameWin();
					}
				}
			}
		}
		
		private function GameWin():void 
		{
			mainScene.removeEventListener(MouseEvent.CLICK, mainScene_click);
			mainScene.removeEventListener(MouseEvent.MOUSE_OVER, mainScene_mouseOver);
			mainScene.removeEventListener(MouseEvent.MOUSE_OUT, mainScene_mouseOut);
			
			for (var j:int = 0; j < mineArray.length; j++) 
			{
				if (!cardArray[mineArray[j]].flaged)
				{
					cardArray[mineArray[j]].addChild(new Flag());
					cardArray[mineArray[j]].flaged = true;
				}
			}
			
			if (timer)
			{
				timer.stop();
				timer.removeEventListener(TimerEvent.TIMER, timer_timer);
				timer = null;
			}
			
			trace("----------You  Win-----------");
			minesLeft_txt.text = "Mines Left: " + "0";
			gameInfo_txt.text = "You  Win!";
		}
		
		private function GameOver():void 
		{
			mainScene.removeEventListener(MouseEvent.CLICK, mainScene_click);
			mainScene.removeEventListener(MouseEvent.MOUSE_OVER, mainScene_mouseOver);
			mainScene.removeEventListener(MouseEvent.MOUSE_OUT, mainScene_mouseOut);
			for (var i:int = 0; i < mineArray.length; i++) 
			{
				if (cardArray[mineArray[i]].flaged)
					cardArray[mineArray[i]].removeChildAt(cardArray[mineArray[i]].numChildren - 1);
				
				cardArray[mineArray[i]].showed = true;
			}
			
			if (timer)
			{
				timer.stop();
				timer.removeEventListener(TimerEvent.TIMER, timer_timer);
				timer = null;
			}
			
			trace("----------Game Over----------");
			trace("----------You  Lose----------");
			gameInfo_txt.text = "You  Lose!";
		}
		
		// 递归除去无雷点
		private function noMinePropagate(card:Card):void 
		{
			if (card.type % 10 == 9)
				return;
			else
			{
				var minerow:int = card.type / 1000;
				var minecol:int = (card.type  % 1000) / 10;
				var roundnum:int = 0;
				var cardObj:Object;
				var j:int = 0;
				
				if ((minerow == 1 && minecol == 1) || (minerow == rows && minecol == cols) || (minerow == 1 && minecol == cols) || (minerow == rows && minecol == 1))
					roundnum = 3;
				else if (minerow == 1 || minerow == rows || minecol == 1 || minecol == cols)
					roundnum = 5;
				else
					roundnum = 8;
				
				if (roundnum == 3)
				{
					
					if (minerow == 1 && minecol == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							decide(j+4);
						}
					}
					
					if (minerow == 1 && minecol == cols)
					{
						for (j = 0; j < roundnum; j++) 
						{
							decide(j+6);
						}
					}
					
					if (minerow == rows && minecol == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							decide(j+2);
						}
					}
					
					if (minerow == rows && minecol == cols)
					{
						decide(j+1);
						decide(j+2);
						decide(j+8);
					}
					
				}
				
				if (roundnum == 5)
				{
					if (minerow == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							decide(j+4);
						}
					}
					
					if (minerow == rows)
					{
						for (j = 0; j < roundnum - 1; j++) 
						{
							decide(j+1);
						}
						
						decide(8);
					}
					
					if (minecol == 1)
					{
						for (j = 0; j < roundnum; j++) 
						{
							decide(j+2);
						}
					}
					
					if (minecol == cols)
					{
						for (j = 0; j < 2; j++) 
						{
							decide(j+1);
						}
						
						for (j = 0; j < 3; j++) 
						{
							decide(j+6);
						}
					}
				}
				
				if (roundnum == 8)
				{
					for (j = 0; j < roundnum; j++) 
					{
						decide(j+1);
					}
				}
				
				function decide(index:int):void
				{
					if (card is Digit)
						return;
					
					var obj:Object = new Object();
					obj.position = card.type;
					cardObj = centreAroundObj(obj, index);
					var positionCard:Card = cardArray[positionToIndex(cardObj.position)];
					
					if (positionCard is Digit && !positionCard.clicked)
					{
						positionCard.clicked = true;
						positionCard.showed = true;
						showedCount++;
						return;
					}
					
					if (positionCard is Mine || positionCard.clicked || positionCard.flaged)
					{
						return;
					}
					
					positionCard.clicked = true;
					positionCard.showed = true;
					showedCount++;
					noMinePropagate(positionCard);
				}
			}
		}
		
		
		// 顺时针
		private function centreAroundObj(centreObj:Object, index:int):Object
		{
			var returnObj:Object = new Object();
			var centrerow:int = centreObj.position / 1000;
			var centrecol:int = (centreObj.position  % 1000) / 10;
			var row:int;
			var col:int;
			
			if (index <= 3)
			{
				row = centrerow - 1;
				col = centrecol + index - 2;
			}
			
			if (index == 4)
			{
				row = centrerow;
				col = centrecol + 1;
			}
			
			if (index >= 5 && index <= 7)
			{
				row = centrerow + 1;
				col = centrecol - (index - 6);
			}
			
			if (index == 8)
			{
				row = centrerow;
				col = centrecol - 1;
			}
			
			returnObj.position = row * 1000 + col * 10;
			returnObj.x = (col - 1) * gridpx;
			returnObj.y = (row - 1) * gridpx;
			
			return returnObj;
		}
		
		private function indexToPosition(index:int):Object
		{
			var positionObj:Object = new Object();
			positionObj.position = Math.ceil(((index + 1) / cols)) * 1000 + (index % cols + 1) * 10; 
			positionObj.x = (index % cols) * gridpx;
			positionObj.y = (Math.ceil(((index + 1) / cols)) - 1) * gridpx;
			return positionObj;
		}
		
		private function positionToIndex(position:int):int
		{
			return ((int)(position / 1000) - 1) * cols + (int)((position % 1000) / 10) - 1;
		}
	}
	
}
